package com.zhang.my.uaa.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.cj.jdbc.MysqlDataSource;
import com.zhang.my.uaa.security.filter.RestAuthenticationFilter;
import com.zhang.my.uaa.security.filter.userdetails.UserDetailsPasswordServiceImpl;
import com.zhang.my.uaa.security.filter.userdetails.UserDetailsServiceImpl;
import com.zhang.my.uaa.security.jwt.JwtFilter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import javax.sql.DataSource;
import java.util.Map;

/**
 * SecurityConfig:Security安全配置类,@Order(99)为了支持表单登录
 *
 * @author zhangxiaoxiang
 * @date 2021/08/15
 */
@Slf4j
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
@Import(SecurityProblemSupport.class)
@Order(99)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final ObjectMapper objectMapper;
    private final SecurityProblemSupport securityProblemSupport;
    private final DataSource dataSource;
    private final UserDetailsServiceImpl userDetailsServiceImpl;
    private final UserDetailsPasswordServiceImpl userDetailsPasswordServiceImpl;
    private final JwtFilter jwtFilter;

    /**
     * 覆盖此方法以配置HttpSecurity 。 通常子类不应通过调用 super 来调用此方法，因为它可能会覆盖其配置。 默认配置为：
     * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
     *
     * @param http http
     * @throws Exception Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // //  授权示例1  基本认证授权
        // http
        //         //显示重写登录页[只是仍然采用默认登录页而已]
        //         .formLogin(Customizer.withDefaults())
        //         //授权角色才能访问
        //         // .authorizeRequests(req -> req.mvcMatchers("/api/greeting").hasRole("ADMIN"));
        //         //授权认证就可以访问
        //         .authorizeRequests(req -> req.antMatchers("/api/greeting").authenticated());
        //    -------------------------------------------------------------------------------------------------
        //  授权示例2 httpBasic认证
        // http
        //         .csrf(AbstractHttpConfigurer::disable)
        //         .httpBasic(Customizer.withDefaults())
        //         .formLogin(form -> form.loginPage("/"));
        //    -------------------------------------------------------------------------------------------------
        //  授权示例3 定制登录页[账号密码在配置文件或代码注入],记住我
        // http
        //         .authorizeRequests(req -> req.anyRequest().authenticated())
        //         //localhost 将您重定向的次数过多 + .permitAll()
        //         .formLogin(form -> form.loginPage("/login").permitAll())
        //         .csrf(withDefaults())
        //         .logout(logout -> logout.logoutUrl("/perform_logout"))
        //         //存储记住我身份验证令牌的 cookie 的名称。 默认为“记住我”。
        //         .rememberMe(rememberMe -> rememberMe.tokenValiditySeconds(30 * 24 * 3600).rememberMeCookieName("someKeyToRemember"))
        // ;
        //    -------------------------------------------------------------------------------------------------
        //    表单登录 定制登录成功或失败处理,返回JSON数据
        //     http
        //             .authorizeRequests(req -> req.anyRequest().authenticated())
        //             //localhost 将您重定向的次数过多 + .permitAll()
        //             .formLogin(form -> form.loginPage("/login")
        //                     .successHandler(jsonLoginSuccessHandler())
        //                     .failureHandler(jsonLoginFailureHandler())
        //                     .permitAll())
        //             .csrf(withDefaults())
        //             .logout(logout -> logout.logoutUrl("/perform_logout"))
        //             //存储记住我身份验证令牌的 cookie 的名称。 默认为“记住我”。
        //             .rememberMe(rememberMe -> rememberMe.tokenValiditySeconds(30 * 24 * 3600).rememberMeCookieName("someKeyToRemember"))
        //     ;
        // }
        //    -------------------------------------------------------------------------------------------------
        //前后端分离登录示例
        // http
        //         .csrf(csrf -> csrf.ignoringAntMatchers("/api/**", "/admin/**", "/authorize/**"))
        //         .authorizeRequests(authorizeRequests -> authorizeRequests
        //                 .antMatchers("/authorize/**").permitAll()
        //                 .antMatchers("/admin/**").hasRole("ADMIN")
        //                 .antMatchers("/api/**").hasRole("USER")
        //                 .anyRequest().authenticated())
        //         .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        //    -------------------------------------------------------------------------------------------------
        //密码验证[2中登录均支持,但是注意记住我]
        // http
        //         .requestMatchers(req -> req.mvcMatchers("/api/**", "/admin/**", "/authorize/**"))
        //         .sessionManagement(sessionManagement -> sessionManagement
        //                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        //         .exceptionHandling(exceptionHandling -> exceptionHandling
        //                 .authenticationEntryPoint(securityProblemSupport)
        //                 .accessDeniedHandler(securityProblemSupport))
        //         .authorizeRequests(authorizeRequests -> authorizeRequests
        //                 .antMatchers("/authorize/**").permitAll()
        //                 .antMatchers("/admin/**").hasRole("ADMIN")
        //                 .antMatchers("/api/**").hasRole("USER")
        //                 .anyRequest().authenticated())
        //         .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        //         .csrf(csrf -> csrf.ignoringAntMatchers("/authorize/**", "/admin/**", "/api/**"))
        //         .formLogin(AbstractHttpConfigurer::disable)
        //         .httpBasic(AbstractHttpConfigurer::disable)
        // ;
        //    -------------------------------------------------------------------------------------------------
        //连接数据库
        // http
        //         .requestMatchers(req -> req.mvcMatchers("/api/**", "/admin/**", "/authorize/**"))
        //         .sessionManagement(sessionManagement -> sessionManagement
        //                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        //         .exceptionHandling(exceptionHandling -> exceptionHandling
        //                 .authenticationEntryPoint(securityProblemSupport)
        //                 .accessDeniedHandler(securityProblemSupport))
        //         .authorizeRequests(authorizeRequests -> authorizeRequests
        //                 .antMatchers("/authorize/**").permitAll()
        //                 .antMatchers("/admin/**").hasRole("ADMIN")
        //                 .antMatchers("/api/**").hasRole("USER")
        //                 .anyRequest().authenticated())
        //         .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        //         .csrf(csrf -> csrf.ignoringAntMatchers("/authorize/**", "/admin/**", "/api/**"))
        //         .formLogin(AbstractHttpConfigurer::disable)
        //         .httpBasic(AbstractHttpConfigurer::disable)
        // ;
        //连接数据库jwt

        http
                .requestMatchers(req -> req.mvcMatchers("/api/**", "/admin/**", "/authorize/**"))
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(securityProblemSupport)
                        .accessDeniedHandler(securityProblemSupport))
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/authorize/**").permitAll()
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        .antMatchers("/api/**").hasRole("USER")
                        .anyRequest().authenticated())
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .csrf(csrf -> csrf.ignoringAntMatchers("/authorize/**", "/admin/**", "/api/**"))
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable);

    }


    /**
     * 自定义过滤器,用于前后端分离的JSON请求登录
     *
     * @return restAuthenticationFilter
     * @throws Exception Exception
     */
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(jsonLoginSuccessHandler());
        filter.setAuthenticationFailureHandler(jsonLoginFailureHandler());
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }

    /**
     * 登录成功定制返回
     *
     * @return AuthenticationSuccessHandler
     */

    private AuthenticationSuccessHandler jsonLoginSuccessHandler() {
        return (req, res, auth) -> {

            res.setStatus(HttpStatus.OK.value());
            log.debug("认证成功");
            res.getWriter().println(objectMapper.writeValueAsString(auth));
        };
    }

    /**
     * 登录失败定制返回
     *
     * @return AuthenticationFailureHandler
     */
    private AuthenticationFailureHandler jsonLoginFailureHandler() {
        return (req, res, exp) -> {
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            res.setCharacterEncoding("UTF-8");
            val errData = Map.of(
                    "title", "认证失败",
                    "details", exp.getMessage()
            );
            log.warn("认证失败");
            res.getWriter().println(objectMapper.writeValueAsString(errData));
        };
    }

    // /**
    //  * 和配置yml一样的
    //  *
    //  * @param auth auth
    //  * @throws Exception Exception
    //  */
    // @Override
    // protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //     auth.inMemoryAuthentication()
    //             .withUser("user")
    //             .password(passwordEncoder().encode("12345678"))
    //             .roles("USER", "ADMIN");
    // }

    /**
     * 注入一个密码加密器
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 默认编码算法的 Id
        val idForEncode = "bcrypt";
        // 要支持的多种编码器
        val encoders = Map.of(
                idForEncode, new BCryptPasswordEncoder(),
                //兼容之前老版本加密器,这个过时是已知的
                "SHA-1", new MessageDigestPasswordEncoder("SHA-1")
        );
        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }

    /**
     * PasswordEncoder 需要
     *
     * @return AuthenticationManager
     * @throws Exception Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 测试密码兼容性和数据库存数据
     *
     * @param auth AuthenticationManagerBuilder
     * @throws Exception Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // auth
        //         //使用数据库方式
        //         .jdbcAuthentication()
        //         // .withDefaultSchema()
        //         .dataSource(dataSource)
        // ;
        //         //使用内存方式
        //         // .inMemoryAuthentication()
        //         // .passwordEncoder(passwordEncoder())
        //         // .withUser("user")
        //         // .password("{bcrypt}$2a$10$jhS817qUHgOR4uQSoEBRxO58.rZ1dBCmCTjG8PeuQAX4eISf.zowm")
        //         // .roles("USER", "ADMIN")
        //         // .and()
        //         // .withUser("old_user")
        //         // .password("{SHA-1}7ce0359f12857f2a90c7de465f40a95f01cb5da9")
        //         // .roles("USER");
        //自定义
        auth
                // 配置 AuthenticationManager 使用 userService
                .userDetailsService(userDetailsServiceImpl)
                // 配置 AuthenticationManager 使用 userService
                .passwordEncoder(passwordEncoder())
                // 配置密码自动升级服务
                .userDetailsPasswordManager(userDetailsPasswordServiceImpl);


    }


    //
    // /**
    //  * 不使用过滤器链的接口配置[静态资源等]
    //  *
    //  * @param web 请求
    //  * @throws Exception 异常
    //  */
    // @Override
    // public void configure(WebSecurity web) throws Exception {
    //     log.info("-----------web放行接口,不需要验证-------------");
    //     web.ignoring().mvcMatchers("/public/**", "/error")
    //             .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    // }


    /**
     * 前后端分离使用
     *
     * @param web WebSecurity
     * @throws Exception Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        log.info("-----------web放行接口,不需要验证-------------");
        web.ignoring().antMatchers("/error/**", "/h2-console/**");
    }
}
